package wci.backend.compiler;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import wci.frontend.*;
import wci.intermediate.*;
import wci.intermediate.icodeimpl.ICodeKeyImpl;
import wci.intermediate.symtabimpl.Predefined;
import wci.intermediate.symtabimpl.SymTabKeyImpl;

import static wci.intermediate.icodeimpl.ICodeKeyImpl.*;
import static wci.intermediate.symtabimpl.SymTabKeyImpl.ROUTINE_SYMTAB;

public class CodeGeneratorVisitor
    extends PclParserVisitorAdapter
    implements eREMParserConstants
{
	public Object visit(ASTassignmentStatement node, Object data)
    {
    	CodeInfo ci = (CodeInfo) data;
    	SimpleNode valueExpressionNode = (SimpleNode) node.jjtGetChild(0);
        SimpleNode targetNode   = (SimpleNode) node.jjtGetChild(1);
        

        // Emit code for the expression.
       
        TypeSpec expressionType = valueExpressionNode.getTypeSpec();

        // Get the assignment type.
        TypeSpec targetType = targetNode.getTypeSpec();
        SymTabEntry targetSymbolTableEntry = (SymTabEntry) targetNode.getAttribute(ID);
	    Definition targetDef = targetSymbolTableEntry.getDefinition();
        String typePrefix = CodeGenerator.GetTypePrefix(targetType);

	    
        if ((expressionType == Predefined.realType) &&
            (expressionType == Predefined.integerType))
        {
            outputObjectCode("i2f",1);
            
        }
        else if (expressionType == Predefined.stringType || expressionType == Predefined.stringConstantType)
        {
	    	        	
        }
        else if ((expressionType == Predefined.uriType))
        {
                  	
        }
        else if ((expressionType == Predefined.regexType))
        {
                    	
        }
        else if ((expressionType == Predefined.showType))
        {
                    	
        }

    	valueExpressionNode.jjtAccept(this, data);
        SymTabEntry variableId = (SymTabEntry) targetNode.getAttribute(ID);
        int index = CodeGenerator.GetVarIndex(variableId.getIndex(), ci.type ) ;

        // Emit the appropriate store instruction.
        outputObjectCode( typePrefix +
                                         "store " + index,1);

        
	           

        return data;
    }


    public Object visit(ASTmultiply node, Object data)
    {
        SimpleNode addend0Node = (SimpleNode) node.jjtGetChild(0);
        SimpleNode addend1Node = (SimpleNode) node.jjtGetChild(1);

        TypeSpec type0 = addend0Node.getTypeSpec();
        TypeSpec type1 = addend1Node.getTypeSpec();

        // Get the addition type.
        TypeSpec type = node.getTypeSpec();
        String typePrefix = (type == Predefined.integerType) ? "i" : "f";

        // Emit code for the first expression
        // with type conversion if necessary.
        addend0Node.jjtAccept(this, data);
        if ((type == Predefined.realType) &&
            (type0 == Predefined.integerType))
        {
            outputObjectCode("i2f",1);
            
        }

        // Emit code for the second expression
        // with type conversion if necessary.
        addend1Node.jjtAccept(this, data);
        if ((type == Predefined.realType) &&
            (type1 == Predefined.integerType))
        {
            outputObjectCode("i2f",1);
            
        }

        // Emit the appropriate add instruction.
        outputObjectCode( typePrefix + "mul",1);
        

        return data;
    }

    public Object visit(ASTdivide node, Object data)
    {
        SimpleNode addend0Node = (SimpleNode) node.jjtGetChild(0);
        SimpleNode addend1Node = (SimpleNode) node.jjtGetChild(1);

        TypeSpec type0 = addend0Node.getTypeSpec();
        TypeSpec type1 = addend1Node.getTypeSpec();

        // Get the addition type.
        TypeSpec type = node.getTypeSpec();
        String typePrefix = (type == Predefined.integerType) ? "i" : "f";

        // Emit code for the first expression
        // with type conversion if necessary.
        addend0Node.jjtAccept(this, data);
        if ((type == Predefined.realType) &&
            (type0 == Predefined.integerType))
        {
            outputObjectCode("i2f",1);
            
        }

        // Emit code for the second expression
        // with type conversion if necessary.
        addend1Node.jjtAccept(this, data);
        if ((type == Predefined.realType) &&
            (type1 == Predefined.integerType))
        {
            outputObjectCode("i2f",1);
            
        }

        // Emit the appropriate add instruction.
        outputObjectCode( typePrefix + "div",1);
        

        return data;
    }
    
    public Object visit(ASTvariable node, Object data)
    {
    	CodeInfo ci = (CodeInfo) data;
        TypeSpec type = node.getTypeSpec();
        
        /*
         * If type is Predefined.undefinedType, that means we don't know the
         * right type.  The data object might have a type, so we check.
         * If there IS a type on the data object has a type, then we check if
         * they match.  This is our type checking. :)
         */
        //if(type == Predefined.undefinedType && ci.expectedTypeSpec != Predefined.undefinedType) {
        //	type = ci.expectedTypeSpec;
        	/* Emit code to initialize this object */
        	
        //}
        //else if(type != Predefined.undefinedType) {
        	// Do type checking
        //}
        
        String typePrefix = CodeGenerator.GetTypePrefix(type);
        SymTabEntry variableId = (SymTabEntry) node.getAttribute(ID);
        int index = CodeGenerator.GetVarIndex(variableId.getIndex(), ci.type);
        
        // Emit the appropriate load instruction.
        outputObjectCode( typePrefix +
                                         "load " + index + " ;load variable " + variableId.getName(),1);
        

        return data;
    }

    public Object visit(ASTintegerConstant node, Object data)
    {
        int value = (Integer) node.getAttribute(VALUE);
        
        // Emit a load constant instruction.        
        outputObjectCode("new java/lang/Integer");
        outputObjectCode("dup");
    	outputObjectCode("ldc " + value + " ; integer constant");
    	outputObjectCode("invokenonvirtual java/lang/Integer.<init>(I)V");

        return data;
    }
    
   
    public Object visit(ASTincrementStatement node, Object data)
    {  
    	SimpleNode expressionNode = (SimpleNode) node.jjtGetChild(0);
        SymTabEntry expTabEntry = (SymTabEntry) expressionNode.getAttribute(ID);
        
        CodeInfo ci = (CodeInfo) data;
        int index = CodeGenerator.GetVarIndex(expTabEntry.getIndex(), ci.type ) ;
        
        TypeSpec type = expTabEntry.getTypeSpec();
        if( type == Predefined.integerType )
        {   
        	//use intValue method of Integer class to convert it into int type.      	
        	outputObjectCode("aload_" + index + " ; loading integer value to increment", 1);        	
        	outputObjectCode("invokevirtual	java/lang/Integer.intValue()I",1);

            outputObjectCode("iconst_1",1);            
            
            // Emit the appropriate add instruction.
            outputObjectCode("iadd",1);
            outputObjectCode("invokestatic	java/lang/Integer.valueOf(I)Ljava/lang/Integer;",1);
            outputObjectCode("astore_" + index ,1);          
        }

        return data;
    }
    
    public Object visit(ASTwriteToFileStatement node, Object data)
    {
      	//if uriIDType save expression into file destination 
        	
        //outputObjectCode("TODO:JASMIN URI-ID LOAD THIS!: " + (String) node.getAttribute(VALUE),1);
        	
    	SimpleNode fileUri = (SimpleNode) node.jjtGetChild(1);
        outputObjectCode("; fileUri",1);
        SimpleNode sourceNode = (SimpleNode) node.jjtGetChild(0);
    
        // Emit code for the expression.
        outputObjectCode("; source",1);
        	
    	outputObjectCode("aload_0 ; this pointer for uri",1);
        	 
        fileUri.jjtAccept(this, data);
    	sourceNode.jjtAccept(this, data);
            
        outputObjectCode("invokevirtual scarlett/ScarlettRuntime/appendToFile(Ljava/lang/Object;Ljava/lang/Object;)Z",1);
        outputObjectCode("pop ; remove the boolean, for now we are not using it.",1);
    	return data;
    }


    public Object visit(ASTstringConstant node, Object data)
    {
        String value = (String) node.getAttribute(VALUE);

        // Emit a load constant instruction.
        outputObjectCode("ldc \"" + value + "\" ; stringConstant");
        

        return data;
    }
    
    public Object visit(ASTrealConstant node, Object data)
    {
        float value = (Float) node.getAttribute(VALUE);

        // Emit a load constant instruction.
        outputObjectCode("ldc " + value + " ; real constant",1);
        

        return data;
    }
    public Object visit(ASTregex node, Object data) 
    {
        String value = (String)  node.getAttribute(VALUE);

        // Emit a load constant instruction.
        outputObjectCode("aload_0 ; this pointer for regex call",1);
        outputObjectCode("ldc \"" + value + "\"",1);
        outputObjectCode("invokevirtual scarlett/ScarlettRuntime/GetPattern(Ljava/lang/String;)Ljava.util.regex.Pattern;",1);
        
        return data;
    }
    public Object visit(ASThttpUri node, Object data)
    {
    	 String value = (String)  node.getAttribute(VALUE);

         // Emit a load constant instruction.
    	 outputObjectCode("ldc \"" + value + "\" ; httpuri",1);
         

         return data;
    }
    public Object visit(ASTfetchExpression node, Object data) 
    {
    	
    	outputObjectCode("aload_0 ; this pointer for Fetch",1);
    	
        SimpleNode regexNode = (SimpleNode) node.jjtGetChild(0);
        
        SimpleNode sourceNode = (SimpleNode) node.jjtGetChild(1);
                // Emit code for the expression.
        
        outputObjectCode("; begin source",1);
        sourceNode.jjtAccept(this, data);
        outputObjectCode("; end source",1);
        outputObjectCode("; begin regex",1);
        regexNode.jjtAccept(this, data);
        outputObjectCode("; end regex",1);
        outputObjectCode("invokevirtual scarlett/ScarlettRuntime/Fetch(Ljava/lang/String;Ljava.util.regex.Pattern;)Ljava.util.List;",1);
        
        return data;
    	
    }
    public Object visit(ASTshow node, Object data)
    {
      //  
    	outputObjectCode("aload_0 ; This ptr for Show command",1);
        SimpleNode expressionNode = (SimpleNode) node.jjtGetChild(0);
        expressionNode.jjtAccept(this, data);
    	outputObjectCode("invokevirtual scarlett/ScarlettRuntime/Show(Ljava/lang/Object;)V",1);
        

        return data;
    }
    
    /**
     * Visits an if statement node and inserts Jasmin code into the object file.
     */
    public Object visit(ASTifStatement node, Object data) {
    	SimpleNode compareNode = null;
    	SimpleNode thenNode = null;
    	try {
    		compareNode = (SimpleNode) node.jjtGetChild(0);
    		thenNode = (SimpleNode) node.jjtGetChild(1);
    	}
    	catch(NullPointerException npe) {
    		// Malformed if, don't write any jasmin
    		return data;
    	}
    	
    	SimpleNode elseNode = null;
    	try {
    		elseNode = (SimpleNode) node.jjtGetChild(2);
    	}
    	catch(ArrayIndexOutOfBoundsException ignored) {
    		// No else node
    	}
        
    	String nextLabel = getNextLabel();
    	
    	if(elseNode == null) { // No else block
    		compareNode.jjtAccept(this, data); // Output object code for the comparison
    		outputObjectCode("ifeq " + nextLabel); // If
    		thenNode.jjtAccept(this, data); // Output object code for the then block
    		outputObjectCode(nextLabel + ":", 0); // Jump to label
    	}
    	else {
    		String falseLabel = getNextLabel();
    		compareNode.jjtAccept(this, data); // Output object code for the comparison
    		outputObjectCode("ifeq " + falseLabel); // If
    		thenNode.jjtAccept(this, data); // Output object code for the then block
    		outputObjectCode("goto " + nextLabel); // Jump to end
    		outputObjectCode(falseLabel + ":", 0);
    		elseNode.jjtAccept(this, data); // Output object code for the else block
    		outputObjectCode(nextLabel + ":", 0);
    	}
        
        return data;
    }
    
    
    /*
     * TODO:do this chris w
     * @see wci.intermediate.PclParserVisitorAdapter#visit(wci.frontend.ASTforStatement, java.lang.Object)
     */
    public Object visit(ASTforStatement node, Object data) 
    {
        CodeInfo ci = (CodeInfo) data;
        
    	String lblTopLoop = getNextLabel();
    	String lblBottomLoop = getNextLabel();
    	
        SimpleNode variableNode = (SimpleNode) node.jjtGetChild(0);
        SimpleNode sourceNode = (SimpleNode) node.jjtGetChild(1);
        SimpleNode loopStatements = (SimpleNode) node.jjtGetChild(2);
                // Emit code for the expression.
        

        TypeSpec varType = variableNode.getTypeSpec();
        String varTypePrefix = CodeGenerator.GetTypePrefix(varType);
        SymTabEntry variableId = (SymTabEntry) variableNode.getAttribute(ID);
        int varIndex = CodeGenerator.GetVarIndex(variableId.getIndex(), ci.type);

        
        TypeSpec sourceType = sourceNode.getTypeSpec();
        String sourceTypePrefix = CodeGenerator.GetTypePrefix(sourceType);
        SymTabEntry sourceId = (SymTabEntry) sourceNode.getAttribute(ID);
        int sourceIndex = CodeGenerator.GetVarIndex(sourceId.getIndex(), ci.type);
         
        sourceNode.jjtAccept(this, data);
        outputObjectCode("astore " + sourceIndex + " ; store the source on TOS into the var",1); 

        
        outputObjectCode("aload " + sourceIndex + " ; store the source on TOS into the var",1);
        outputObjectCode("; get the iterator " ,1);
        outputObjectCode("invokeinterface java.util.List/iterator()Ljava/util/Iterator; 1",1);
        outputObjectCode(lblTopLoop + ":",1);

        outputObjectCode("dup",1);
        outputObjectCode("dup",1);
    	

        outputObjectCode("; get the next value " ,1);
        outputObjectCode("invokeinterface java.util.Iterator/next()Ljava/lang/Object; 1",1);
        outputObjectCode("checkcast " + CodeGenerator.GetTypeCodeWithoutL(varType)); 
        outputObjectCode("astore " + varIndex,1);
    	
        outputObjectCode("; Loop statements" ,1);
        
        
        loopStatements.jjtAccept(this, data);
        //outputObjectCode("aload_0",1);
        //outputObjectCode("aload " + varIndex,1);
        //outputObjectCode("invokevirtual scarlett/ScarlettRuntime/Show(Ljava/lang/Object;)V",1);
       
        outputObjectCode(";End Loop statements" ,1);
        outputObjectCode("invokeinterface java.util.Iterator/hasNext()Z 1",1);
      
        outputObjectCode("ifne " + lblTopLoop + " ; bottom of loop",1); 
        
    	

        return data;
    	
    }

    
    
    
    
    /**
     * Visits an "is same as" node and inserts Jasmin code into the object file.
     */
    public Object visit(ASTIS_SAME_AS node, Object data) {
    	SimpleNode firstNode = (SimpleNode) node.jjtGetChild(0);
    	SimpleNode secondNode = (SimpleNode) node.jjtGetChild(1);
    	outputObjectCode("aload_0");
    	firstNode.jjtAccept(this, data);
    	secondNode.jjtAccept(this, data);
    	outputObjectCode("invokevirtual scarlett/ScarlettRuntime/IsSameAs(Ljava/lang/Object;Ljava/lang/Object;)Z");
    	return data;
    }
    
    /**
     * Visits an "is a" node and inserts Jasmin code into the object file.
     */
	public Object visit(ASTIS_A node, Object data) {
		SimpleNode valueNode = (SimpleNode) node.jjtGetChild(0);
    	SimpleNode regexNode = (SimpleNode) node.jjtGetChild(1);
    	outputObjectCode("aload_0");
    	valueNode.jjtAccept(this, data);
    	regexNode.jjtAccept(this, data);
    	outputObjectCode("invokevirtual scarlett/ScarlettRuntime/IsA(Ljava/lang/Object;Ljava/util/regex/Pattern;)Z");
		return data;
	}
	
	/**
     * Visits an "contains" node and inserts Jasmin code into the object file.
     */
	public Object visit(ASTCONTAINS node, Object data) {
		SimpleNode firstNode = (SimpleNode) node.jjtGetChild(0);
    	SimpleNode secondNode = (SimpleNode) node.jjtGetChild(1);
    	outputObjectCode("aload_0");
    	firstNode.jjtAccept(this, data);
    	secondNode.jjtAccept(this, data);
    	outputObjectCode("invokevirtual scarlett/ScarlettRuntime/Contains(Ljava/lang/Object;Ljava/lang/Object;)Z");
    	return data;
	}
	
	/**
     * Visits an "is larger than" node and inserts Jasmin code into the object file.
     */
	public Object visit(ASTIS_LARGER_THAN node, Object data) {
		SimpleNode firstNode = (SimpleNode) node.jjtGetChild(0);
    	SimpleNode secondNode = (SimpleNode) node.jjtGetChild(1);
    	outputObjectCode("aload_0");
    	firstNode.jjtAccept(this, data);
    	secondNode.jjtAccept(this, data);
    	outputObjectCode("invokevirtual scarlett/ScarlettRuntime/IsLargerThan(Ljava/lang/Object;Ljava/lang/Object;)Z");
    	return data;
	}

	/**
     * Visits an "is smaller than" node and inserts Jasmin code into the object file.
     */
	public Object visit(ASTIS_SMALLER_THAN node, Object data) {
		SimpleNode firstNode = (SimpleNode) node.jjtGetChild(0);
    	SimpleNode secondNode = (SimpleNode) node.jjtGetChild(1);
    	outputObjectCode("aload_0");
    	firstNode.jjtAccept(this, data);
    	secondNode.jjtAccept(this, data);
    	outputObjectCode("invokevirtual scarlett/ScarlettRuntime/IsSmallerThan(Ljava/lang/Object;Ljava/lang/Object;)Z");
    	return data;
	}
    
    /**
     * Output the specified object code.
     * Will work best with a single line of code.
     * Defaults to an indentation level of 1.
     * @param code the code to output.
     */
    private void outputObjectCode(String code) {
    	outputObjectCode(code, 1);
    }
    
    /**
     * Output the specified object code at the specified indentation level.
     * Will work best with a single line of code.
     * @param code the code to output.
     * @param identLevel the indentation to use.
     */
    private void outputObjectCode(String code, int identLevel) {
    	StringBuilder sb = new StringBuilder();
    	for(int i = 0; i < identLevel; i++) sb.append('\t');
    	sb.append(code);
    	CodeGenerator.objectFile.println(sb.toString());
    }
    
    /**
     * Output code that will be a part of the initialization block of the
     * current method.
     * @param code
     */
    private void outputInitializationCode(String code) {
    	
    }
    
    /**
     * Generates & gets the next available label.
     * @return the newest label generated.
     */
    private String getNextLabel() {
    	return String.format(LABEL_FORMAT_STRING, LABEL_COUNT++);
    }
    
    
    
    @Override
	public Object visit(ASTfileUri node, Object data) {
	   	 String value = (String)  node.getAttribute(VALUE);
	
	     // Emit a load constant instruction.
		 outputObjectCode("ldc \"" + value + "\" ; fileuri",1);
      	
		return data;
	}



	/* (non-Javadoc)
	 * @see wci.intermediate.PclParserVisitorAdapter#visit(wci.frontend.ASTmethodCall, java.lang.Object)
	 */
	@Override
	public Object visit(ASTmethodCall node, Object data) {
		outputObjectCode("aload_0");
		node.childrenAccept(this, data); // Accept the children to put them all on the stack properly
		StringBuilder builder = new StringBuilder();
		builder.append("invokevirtual ");
		builder.append(CodeGenerator.programName);
		builder.append("/");
		builder.append(node.getAttribute(VALUE));
		builder.append("(");
		
		// TODO: Print out the variable types
		// For now, we'll just write "Object" for each.  This may need to be changed later depending on how method bodies are handled.
		//SymTabEntry nodeEntry = (SymTabEntry)node.getAttribute(ID);
		//SymTab nodeLocalStack = (SymTab) nodeEntry.getAttribute(ROUTINE_SYMTAB);
		//ArrayList<SymTabEntry> locals = nodeLocalStack.sortedEntries();
		int numChildren = node.jjtGetNumChildren();
		for(int i = 0; i < numChildren; i++) {
			builder.append("Ljava/lang/Object;");
		}
		
		// TODO: Will this always be a V?
		builder.append(")V");
		outputObjectCode(builder.toString());
		return data;
	}
	
	@Override
	public Object visit(ASTinsertStatement node, Object data) {
		SimpleNode fromNode = (SimpleNode) node.jjtGetChild(0);
    	SimpleNode targetNode = (SimpleNode) node.jjtGetChild(1);
    	
    	checkForListInNode(targetNode, data);
	    
	    outputObjectCode("aload_0");
	    fromNode.jjtAccept(this, data);
	    targetNode.jjtAccept(this, data);
		outputObjectCode("invokevirtual scarlett/ScarlettRuntime/Insert(Ljava/lang/Object;Ljava/lang/Object;)V");
		return data;
	}
	
	@Override
	public Object visit(ASTremoveStatement node, Object data) {
		SimpleNode fromNode = (SimpleNode) node.jjtGetChild(0);
    	SimpleNode targetNode = (SimpleNode) node.jjtGetChild(1);
    	
    	checkForListInNode(targetNode, data);
	    
	    outputObjectCode("aload_0");
	    fromNode.jjtAccept(this, data);
	    targetNode.jjtAccept(this, data);
		outputObjectCode("invokevirtual scarlett/ScarlettRuntime/Remove(Ljava/lang/Object;Ljava/lang/Object;)V");
		
		return data;
	}
	
	@Override
	public Object visit(ASTjoinStatement node, Object data) {
		SimpleNode firstNode = (SimpleNode) node.jjtGetChild(0);
		SimpleNode secondNode = (SimpleNode) node.jjtGetChild(1);
		SimpleNode destNode = (SimpleNode) node.jjtGetChild(2);
		
		checkForListInNode(destNode, data);
		
		outputObjectCode("aload_0");
		firstNode.jjtAccept(this, data);
		secondNode.jjtAccept(this, data);
		destNode.jjtAccept(this, data);
		outputObjectCode("invokevirtual scarlett/ScarlettRuntime/Join(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V");
		
		return data;
	}

	/**
	 * Will check if the list exists.  If not, it will add code to the object file to init it as an ArrayList.
	 * @param listNode
	 * @param codeInfoContext needed for jjtAccept
	 */
	private void checkForListInNode(SimpleNode listNode, Object codeInfoContext) {
		String label = getNextLabel();    	
		SymTabEntry variableId = (SymTabEntry) listNode.getAttribute(ID);
	    int index = CodeGenerator.GetVarIndex(variableId.getIndex(), ((CodeInfo)codeInfoContext).type);

		outputObjectCode("");
	    outputObjectCode("; Begin check for variable " + variableId.getName());
		listNode.jjtAccept(this, codeInfoContext);
    	outputObjectCode("ifnonnull " + label);
		outputObjectCode("new java/util/ArrayList");
	    outputObjectCode("dup");
	    outputObjectCode("invokespecial java/util/ArrayList.<init>()V");
	    outputObjectCode("astore " + index);
	    outputObjectCode("");
	    outputObjectCode(label + ":", 0);
	}
	
	@Override
	/**
	 * Ignores the children of a method body so that they don't end up in the main code.
	 */
	public Object visit(ASTmethodBody node, Object data) {
		// TODO Auto-generated method stub
		return data;
	}

	private static int LABEL_COUNT = 0;
    private static final String LABEL_FORMAT_STRING = "L%03d";
}
